Imports IICModel
Imports org.codegen.common.ProgressIndicator

Namespace PeriodProcessing

    Public Class StudentPeriodProcessReverser
        Inherits PeriodProcessReverser

        ''' <summary>
        ''' Constructor
        ''' </summary>
        ''' <param name="period">The period to reverse</param>
        Sub New(ByVal period As Period)

            MyBase.New(period)

        End Sub

        ''' <summary>
        ''' Constructor
        ''' </summary>
        ''' <param name="period">The period to reverse</param>
        Sub New(ByVal period As Period, ByVal student As Student)

            MyBase.New(period)
            Me.ReversalRecordsProvider = New StudentReversalRecordsProvider(student)

        End Sub

        Sub New(ByVal cm As ClassMember)

            MyBase.New(cm.PeriodClass.Period)
            Me.ReversalRecordsProvider = New StudentReversalRecordsProvider(cm.Student)

        End Sub

    End Class


    ''' <summary>
    ''' Class that reverses the period processing completed by a PeriodProcessor
    ''' undoing all ceretificates and diplomas earned
    ''' </summary>
    ''' <remarks>
    ''' Periods can be reversed and re-run by menu "Reverse Period Processing".
    ''' This will:
    ''' <ol>
    ''' <li>Update all class members records of the Period process to be reversed to null</li>
    ''' <li>Remove all Diplomas and Certificates earned during this period</li>
    ''' <li>Remove all DiplomasSatisfiedRequirements and CertificateSatisfiedRequirements earned during this period</li>
    ''' <li>Set reversal date field to current date/time</li>
    ''' </ol>
    ''' Field ReversalDate indicates if and when the period process was reversed / cancelled. 
    ''' When periods process records are reversed, and the user runs the period 
    ''' processor again on the same period, a new PeriodProcess is created.
    ''' </remarks>
    Public Class PeriodProcessReverser
        Implements IProgressable


        Property classMemberMapper As New ClassMemberDBMapper
        Property diplomaSatisfiedMapper As New DiplomaSatisfiedRequirementDBMapper
        Property certSatReqMapper As New CertificationSatisfiedRequirementDBMapper
        Property diplomaEarnedMapper As New DiplomaEarnedDBMapper
        Property studentLessonMapper As New StudentLessonCreditUsageDBMapper
        Property periodProcessMapper As New PeriodProcessDBMapper
        Property certificationEarnedMapper As New CertificationEarnedDBMapper
        Property periodMapper As New PeriodDBMapper

        Public Property ReversalRecordsProvider As IReversalRecordsProvider

        Public Property CertificationSatisfiedRequirements() As List(Of CertificationSatisfiedRequirement)

        Public Property CertificationsEarned() As List(Of CertificationEarned)

        Public Property ClassMembers() As List(Of ClassMember)

        Public Property DiplomaSatisfiedRequirements() As List(Of DiplomaSatisfiedRequirement)

        Public Property DiplomasEarned() As List(Of DiplomaEarned)

        Public Property Period() As Period

        ''' <summary>
        ''' Constructor
        ''' </summary>
        ''' <param name="period">The period to reverse</param>
        Sub New(ByVal period As Period)

            'reload from database! do not just take the period object, 
            'which may have unsaved changes
            Me._Period = PeriodDataUtils.findByKey(period.PeriodId)

        End Sub



        ''' <summary>
        ''' Enable the outside world to set the period
        ''' </summary>
        ''' <param name="p">Period</param>
        ''' <remarks></remarks>
        Public Sub setPeriod(ByVal p As Period)
            Me._Period = p
        End Sub

        Sub reversePeriodProcess()

            Dim pps As List(Of PeriodProcess) = PeriodProcessDataUtils.findList("ReversalDate is null and PeriodId={0}", Me._Period.PeriodId)

            If pps.Count > 0 Then

                Dim totalSteps As Integer = pps.Count * 3
                RaiseEvent processStart(Me, New ProgressableEventArgs(totalSteps))

                Dim i As Integer

                For Each pp As PeriodProcess In pps
                    i = i + 1

                    RaiseEvent processGoing(Me, New ProgressableEventArgs(i * 1, totalSteps, "Check if period can be reversed"))
                    Me.checkIfCanBeReversed(pp)

                    RaiseEvent processGoing(Me, New ProgressableEventArgs(i * 2, totalSteps, "Gathering Records..."))
                    Me.gatherRecordsForReversal(pp)

                    RaiseEvent processGoing(Me, New ProgressableEventArgs(i * 3, totalSteps, "Saving to database..."))
                    Me.persistToDatabase(pp)

                Next
            End If

            RaiseEvent processFinished(Me, New ProgressableEventArgs())

        End Sub

        ''' <summary>
        ''' A period can be reversed only if there are no future periods 
        ''' that are processed
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub checkIfCanBeReversed(ByVal _periodProcess As PeriodProcess)

            If Me._Period.IsOpen = False Then
                Throw New ApplicationException("Period is closed and cannot be reversed.")
            End If

            Dim nxtPerid As Integer = 0
            Do While True

                Dim nextPeriod As Period = _
                    Me._Period.getNextPeriod(Me._Period.DiplomaSuperTypeId.GetValueOrDefault)

                If nextPeriod Is Nothing OrElse nxtPerid = nextPeriod.PeriodId Then
                    Exit Do
                End If
                nxtPerid = nextPeriod.PeriodId
                If nextPeriod.isProcessed = True Then
                    Throw New ApplicationException( _
                        String.Format("Period {0} cannot be reversed.  Future periods are already processed, eg: {1}.", _
                                     Me._Period.PeriodDescription, nextPeriod.PeriodDescription))
                End If

            Loop

        End Sub

        Private Sub gatherRecordsForReversal(ByVal periodProcess As PeriodProcess)

            If Me.ReversalRecordsProvider Is Nothing Then
                Me.ReversalRecordsProvider = New ReversalRecordsProvider
            End If

            Me.ReversalRecordsProvider.setReversalRecords(Me, periodProcess)

        End Sub

        Private Sub persistToDatabase(ByVal _periodProcess As PeriodProcess)

            'flag to indicate whether we started a transaction
            Dim transStarted As Boolean = False

            Try

                'check if we are already in transaction, if not then start one
                If ModelContext.CurrentDBUtils().inTrans = False Then
                    ModelContext.beginTrans()
                    transStarted = True
                End If
                Me.ClassMembers.ForEach(Sub(x)
                                            x.PeriodProcessId = Nothing
                                        End Sub)

                'clear PeriodProcessId from members
                Me.classMemberMapper.saveList(_ClassMembers)

                Me.DiplomaSatisfiedRequirements.ForEach(Sub(x)
                                                            x.DiplomaEarnedId = Nothing
                                                            x.CertificationEarnedId = Nothing
                                                        End Sub)

                Me.diplomaSatisfiedMapper.saveList(_DiplomaSatisfiedRequirements)

                Me.CertificationSatisfiedRequirements.ForEach(Sub(x)
                                                                  x.CertificationEarnedId = Nothing
                                                              End Sub)

                Me.certSatReqMapper.saveList(_CertificationSatisfiedRequirements)

                Me.DiplomasEarned.ForEach(Sub(x)

                                              diplomaEarnedMapper.delete(x)
                                          End Sub)

                Me.DiplomaSatisfiedRequirements.ForEach(Sub(x)
                                                            diplomaSatisfiedMapper.delete(x)
                                                        End Sub)

                Me.CertificationsEarned.ForEach( _
                       Sub(x)
                           x.CertificationSatisfiedRequirements. _
                                 ToList.ForEach( _
                                       Sub(y)
                                           'this updates all certification requirements 
                                           'of periods processed other than the one 
                                           'we are reversing
                                           y.CertificationEarnedId = Nothing
                                       End Sub)
                           certSatReqMapper.saveList(x.CertificationSatisfiedRequirements)
                           certificationEarnedMapper.delete(x)
                       End Sub)

                Me.CertificationSatisfiedRequirements.ForEach( _
                            Sub(x)

                                certSatReqMapper.delete(x)

                                If x.StudentLessonExceptionUsageId IsNot Nothing Then
                                    Dim sleu As StudentLessonCreditUsage = _
                                            studentLessonMapper.findByKey( _
                                                    x.StudentLessonExceptionUsageId.GetValueOrDefault)

                                    studentLessonMapper.delete(sleu)
                                End If

                            End Sub)

                'set ReversalDate date to now
                _periodProcess.ReversalDate = Now
                periodProcessMapper.save(_periodProcess)

                If transStarted = True Then
                    'commit here if we started a txn
                    ModelContext.commitTrans()
                End If

            Finally
                If transStarted = True Then
                    'rollback here if we started a txn
                    ModelContext.rollbackTrans()
                End If
            End Try

        End Sub

#Region "IProgressable"

        Public Event processFinished(ByVal sender As Object, ByVal e As ProgressableEventArgs) _
                Implements IProgressable.processFinished

        Public Event processGoing(ByVal sender As Object, ByVal e As ProgressableEventArgs) _
                Implements IProgressable.processGoing

        Public Event processStart(ByVal sender As Object, ByVal e As ProgressableEventArgs) _
                Implements IProgressable.processStart

        Public Property wasCancelled As Boolean Implements IProgressable.wasCancelled

#End Region

        Public Sub startProcess() Implements IProgressable.startProcess

        End Sub
    End Class

End Namespace
